#ifndef B2D_LOGGER_H
#define B2D_LOGGER_H

#include "Singleton.h"
#include <mutex>

NS_B2D_BEGIN


class ILoggerOutput;

////////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief	Logging level.
////////////////////////////////////////////////////////////////////////////////////////////////////
enum class B2D_API LogLevel
{
	LOG_DEBUG,
	LOG_WARNING,
	LOG_CRITICAL
};

////////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief	Logger class
/// 		The logger itself does not output any messages but defers the
/// 		logging to the registered outputs (log file, in-game screen...).
////////////////////////////////////////////////////////////////////////////////////////////////////
class B2D_API Logger final
	: public Singleton<Logger>
{
public:
	enum { MAX_OUTPUTS = 5 };
private:
	enum { BUFFER_SIZE = 1024*16 };

	ILoggerOutput* mOutputs[MAX_OUTPUTS];
	mutable std::mutex mOutputMutex;

public:
	Logger(void);
	~Logger(void);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// \brief	Log a message.
	/// 		This should not be called directly, but instead using
	/// 		the provided macros.
	///
	/// \param	level   	The log level.
	/// \param	file		The source file.
	/// \param	line		The source line.
	/// \param	function	The source function.
	/// \param	msg   		The message description.
	////////////////////////////////////////////////////////////////////////////////////////////////////
	void log(LogLevel level, const string& file, int line, const string& function, const char* msg, ...);
	void log0(LogLevel level, const string& file, int line, const string& function, const string& msg);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// \brief	Registers a listener (output)
	///
	/// \param	output	The logger output.
	////////////////////////////////////////////////////////////////////////////////////////////////////
	void registerOutput(ILoggerOutput& output);
	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// \brief	Unregisters the output described by output.
	///
	/// \param	output	The output to unregister.
	////////////////////////////////////////////////////////////////////////////////////////////////////
	void unregisterOutput(ILoggerOutput& output);
	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// \brief	Unregisters all outputs.
	////////////////////////////////////////////////////////////////////////////////////////////////////
	void unregisterAllOutputs(void);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// \brief	Gets output count.
	///
	/// \return	The output count.
	////////////////////////////////////////////////////////////////////////////////////////////////////
	size_t getOutputCount(void) const;
};


////////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief	Logger output.
/// 		Can be registered with the logger to peform logging
////////////////////////////////////////////////////////////////////////////////////////////////////
class B2D_API ILoggerOutput
{
	B2D_INTERFACE(ILoggerOutput)

public:
	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// \brief	Perform logging (or other action)
	///
	/// \param	level   	The log level.
	/// \param	msg   		The message description.	
	/// \param	file		The source file.
	/// \param	line		The source line.
	/// \param	function	The source function.			
	////////////////////////////////////////////////////////////////////////////////////////////////////
	virtual void log(LogLevel level, const string& msg, const string& file, int line, const string& function) = 0;
};

#ifndef B2D_NO_LOGGING
#define B2D_LOG(level, msg, ...) ::b2d::Logger::Get().log(level, __FILE__, __LINE__, __FUNCTION__, msg, ##__VA_ARGS__)
#define B2D_LOG0(level, msg) ::b2d::Logger::Get().log(level, __FILE__, __LINE__, __FUNCTION__, msg)
#else
#define B2D_LOG(...)
#define B2D_LOG0(...)
#endif

#define B2D_LOG_WARNING(msg, ...) B2D_LOG(::b2d::LogLevel::LOG_WARNING, msg, ##__VA_ARGS__)
#define B2D_LOG_WARNING0(msg) B2D_LOG(::b2d::LogLevel::LOG_WARNING, msg)
#define B2D_LOG_ERROR(msg, ...) B2D_LOG(::b2d::LogLevel::LOG_CRITICAL, msg, ##__VA_ARGS__)
#define B2D_LOG_ERROR0(msg) B2D_LOG(::b2d::LogLevel::LOG_CRITICAL, msg)
#if defined(B2D_DEBUG)
#define B2D_LOG_DEBUG(msg, ...) B2D_LOG(::b2d::LogLevel::LOG_DEBUG, msg, ##__VA_ARGS__)
#define B2D_LOG_DEBUG0(msg) B2D_LOG(::b2d::LogLevel::LOG_DEBUG, msg)
#else
#define B2D_LOG_DEBUG(msg, ...)
#define B2D_LOG_DEBUG0(msg)
#endif

NS_B2D_END

#endif